package com.hoolai.qps.netty.proto;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.google.protobuf.GeneratedMessage;
import com.hoolai.qps.bo.Player.Resp;
import com.hoolai.qps.codes.netty.LengthFieldBasedFrameDecoder;
import com.hoolai.qps.codes.netty.logic.LogicServerChannelHandler;
import com.hoolai.synroom.config.CmdsConfig;
import com.hoolai.synroom.config.ReqCmdProperties;
import com.hoolai.synroom.dispatcher.CmdDispatcher;
import com.hoolai.synroom.dispatcher.SynRoomBusinessCommand;
import com.hoolai.synroom.threadpool.GlobalBusinessThreadPool;
import com.hoolai.synroom.threadpool.SynRoomBusinessThreadPool;

public class LogicServer {

    private static final Logger logger = Logger.getLogger(LogicServer.class);

    private static final int registerCmdId = 0;

    public static final int serverId = 100;

    private final CmdsConfig CMDS_CONFIG;

    public LogicServer(CmdsConfig cmdsConfig) {
        this.CMDS_CONFIG = cmdsConfig;
    }

    public static void main(String[] args) {
        CmdsConfig cmdsConfig = new CmdsConfig("cmd/reqCMD.xml", "cmd/respCMD.xml", logger);
        new LogicServer(cmdsConfig).start(new InetSocketAddress("127.0.0.1", 1111));
    }

    private void start(InetSocketAddress address) {
        ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors
                .newCachedThreadPool(), Executors.newCachedThreadPool()));

        SynRoomBusinessThreadPool businessThreadPool = new SynRoomBusinessThreadPool(32, 64, 50000);
        GlobalBusinessThreadPool globalBusinessThreadPool = new GlobalBusinessThreadPool(5, 10);

//        final TestExceptionHandler exceptionHandler = new TestExceptionHandler();//TODO $

        final CmdDispatcher dispatcher = new CmdDispatcher(CMDS_CONFIG, businessThreadPool, globalBusinessThreadPool,
                null);

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        dispatcher.init(ac);

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(new LengthFieldBasedFrameDecoder(81920, 0, 4, 0, 4, true), new Decoder(),
                        new Encode(CMDS_CONFIG), new LogicServerChannelHandler(dispatcher, null));
            }
        });
        ChannelFuture future = bootstrap.connect(address);//connect proxy
        Channel channel = future.awaitUninterruptibly().getChannel();

        Resp resp = new Resp(registerCmdId, serverId);//register logic server on proxy

        channel.write(resp);//send msg to proxy server
        System.err.println("logic server start!");
    }

    private class Decoder extends OneToOneDecoder {
        @Override
        protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
            ChannelBuffer buffer = (ChannelBuffer) msg;
            int cmdIndex = buffer.readInt() % 10000;
            int channelId = buffer.readInt();
            byte[] bytes = new byte[buffer.readableBytes()];
            buffer.getBytes(8,bytes);

            ChannelBuffer buf = buffer;//TODO $
            if (buf.hasArray()) {
                ReqCmdProperties cmdProperties = CMDS_CONFIG.getReqCmds()[cmdIndex];

                if (cmdProperties == null) {
                    logger.warn("receive msg meet an unsupport cmd index:" + cmdIndex);
                    return null;
                }

                GeneratedMessage proto = (GeneratedMessage) cmdProperties.getDefaultInstance().newBuilderForType()
                        .mergeFrom(buf.array(), buf.arrayOffset() + buf.readerIndex(), buf.readableBytes()).build();
                SynRoomBusinessCommand businessCommand = new SynRoomBusinessCommand(cmdProperties, proto, channelId);

                logger.debug("------------------------------receive msg----------------------------------");
                logger.debug(businessCommand.getProto().toString());
                logger.debug("------------------------------receive msg end----------------------------------");
                return businessCommand;
            } else {
                logger.warn("receive msg meet a null buf");
            }
            return null;

            //Ping ping = Ping.parseFrom(bytes);
            //LogicResponse cmd = new LogicResponse(cmdId, channelId, ping);

            //return cmd;
        }
    }

    private class Encode extends OneToOneEncoder {

        private final CmdsConfig CMDS_CONFIG;

        public Encode(CmdsConfig cmdsConfig) {
            this.CMDS_CONFIG = cmdsConfig;
        }

        @Override
        protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
            Resp resp = (Resp) msg;
            byte[] bytes = null;
            int cmdId = 0;
            if (resp.getMessage() != null) {
                bytes = resp.getMessage().toByteArray();
                cmdId = CMDS_CONFIG.getRespCmdId(resp.getMessage().getClass().getName());
            } else {
                bytes = new byte[0];
            }
            ChannelBuffer buffer = ChannelBuffers.buffer(4 + 4 + 4 + bytes.length);
            buffer.writeInt(bytes.length + 4 + 4);//4 cmdid 4 channelid
            buffer.writeInt(cmdId);//TODO $  cmdId
            buffer.writeInt(resp.getChannelId());
            buffer.writeBytes(bytes);
            return buffer;
        }
    }

}
